/**
 * CC2003
 * 
 * For the course CC2003 - Algorithms and Data Structures of UVG (Universidad del Valle de Guatemala, http://uvg.edu.gt)
 * 
 * 
 * 
 * SVN Repository of my course projects: http://cc2003.svn.sourceforge.net/viewvc/cc2003/
 * 
 * 
 * Copyright (C) 2009 Carlos Lopez Camey
 * visit me at http://kmels.net or write me at c.lopez@kmels.net
 * 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/> or write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package main;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import esquemas.*;

/**
 * @author kmels
 *
 */
public class MoculatorServer implements MoculatorServerInterface{
	private ServerSocket serverSocket;
	private Hashtable<String, Sistema> sistemas;
	private Unmarshaller unmarshaller;
	public PrintWriter notificacionAlCliente;
	
	public MoculatorServer() throws JAXBException{
		this.sistemas = new Hashtable<String,Sistema>();
		
		 // create a JAXBContext capable of handling classes generated into
        // the "esquemas" package
        JAXBContext jc = JAXBContext.newInstance( "esquemas" );
        
        // create an Unmarshaller
        this.unmarshaller = jc.createUnmarshaller();
	}
	/* (non-Javadoc)
	 * @see main.MoculatorServerInterface#crearSistema(java.lang.String, esquemas.ColeccionCampos)
	 */
	@Override
	public void crearSistema(String nombreSistema, ColeccionCampos campos) {
		System.out.println("Va a crear el sistema "+nombreSistema);
		sistemas.put(nombreSistema, new Sistema(nombreSistema,campos));
		System.out.println("Creo el sistema "+nombreSistema);
	}

	/* (non-Javadoc)
	 * @see main.MoculatorServerInterface#escuchar(java.lang.String, int)
	 */
	@Override
	public FileInputStream escuchar(int puerto) throws PuertoNoEscuchableException, IOException, ClassNotFoundException, JAXBException {
		
		System.out.println("Abriendo conexion para escuchar en el puerto "+puerto);
		try {
			//crear el socket del servidor
			this.serverSocket = new ServerSocket(puerto);
		} catch (IOException e){
			throw new PuertoNoEscuchableException("No se puede escuchar en el puerto "+puerto+"..");
		}
		
		System.out.println("Escuchando en el puerto "+puerto+"...");
		//esperar al cliente
		Socket clientSocket = null;
		PrintWriter notificacionAlCliente = null;
		try {
			clientSocket = this.serverSocket.accept();
			System.out.println("Cliente "+clientSocket.getInetAddress()+" se ha conectado.");
			clientSocket.setSoTimeout(1000000);
			notificacionAlCliente = new PrintWriter(clientSocket.getOutputStream(), true);
			notificacionAlCliente.println("MoculatorServer: Se recibio su solicitud de conexion");
		} catch (IOException e){
			System.out.println("Error, el cliente no pudo conectarse");
		}
		
		notificacionAlCliente.println("MoculatorServer: esperando archivo XML con sus instrucciones");
		
		String nombreDelArchivo = Long.toString(System.currentTimeMillis())+".xml";
	    FileOutputStream archivosalida = new FileOutputStream(nombreDelArchivo);
	    BufferedOutputStream escribidorDeArchivo = new BufferedOutputStream(archivosalida);
	    InputStream streamDeEntrada = clientSocket.getInputStream();
	    
	    /**
	     * Referencia
	     * http://forums.sun.com/thread.jspa?threadID=5372011
	     * http://www.jguru.com/forums/view.jsp?EID=1280668
	     * http://www.rgagnon.com/javadetails/java-0542.html
	     */
	    
	    //tratar de leer el archivo
		int filesize=6022386; // filesize temporary hardcoded
		int bytesLeidos;
	    byte [] arraydeBytes  = new byte [filesize];
	    int size = 0;
	    //bytesLeidos = streamDeEntrada.read(arraydeBytes,0,arraydeBytes.length);
	    //leyendoActual = bytesLeidos;
	    
	    
	    try {
	    	while ( (bytesLeidos=streamDeEntrada.read(arraydeBytes)) != -1){
	    		size += bytesLeidos;
	    		escribidorDeArchivo.write(arraydeBytes,0,bytesLeidos);
	    	}
	    	
	    	escribidorDeArchivo.flush();
	    } catch (IOException e){
	    	System.out.println("IO exception");
	    } finally {
	    	if (escribidorDeArchivo != null) escribidorDeArchivo.close();
	    }
	    
	    System.out.println("MoculatorServer: archivo recibido. Procesando..");
	    
	    clientSocket.close();
	    this.serverSocket.close();
	    
	    
	    FileInputStream archivoRecibido = new FileInputStream(nombreDelArchivo);
	    return archivoRecibido;
	}

	/* (non-Javadoc)
	 * @see main.MoculatorServerInterface#ingresarRegistroEn(java.lang.String, esquemas.DefinicionCampos, esquemas.Registro)
	 */
	@Override
	public void ingresarRegistroEn(String nombreSistema,
			DefinicionCampos queCampos, Registro valores) throws SistemaQueEspecificoNoExisteExcepcion, CampoEspecificadoNoEstaDefinidoException, NumeroDeCamposNoCoincidentesConValores, RegistroYaTieneCampoIndexado {
		
		Sistema sistemaEnDondeIngresar = (Sistema) this.sistemas.get(nombreSistema);
		if (sistemaEnDondeIngresar!=null){
			this.sistemas.get(nombreSistema).IngresarRegistro(queCampos, valores);		
		} else{
			throw new SistemaQueEspecificoNoExisteExcepcion("no existe el sistema "+nombreSistema+" especificado en Ingresar");
		}
	}

	/* (non-Javadoc)
	 * @see main.MoculatorServerInterface#modificarRegistro(java.lang.String, esquemas.DefinicionCampos, esquemas.Registro)
	 */
	@Override
	public void modificarRegistro(String nombreSistema,
			DefinicionCampos queCampos, List<Registro> valores) {
		// TODO Auto-generated method stub
		
	}

	/* (non-Javadoc)
	 * @see main.MoculatorServerInterface#procesarRequest(esquemas.Instrucciones)
	 */
	@Override
	public void procesarRequest(Instrucciones instruccionesObjeto) throws SistemaQueEspecificoNoExisteExcepcion, CampoEspecificadoNoEstaDefinidoException, NumeroDeCamposNoCoincidentesConValores, RegistroYaTieneCampoIndexado, CampoNoEstaIndexado, IOException {
		List<Object> instrucciones = instruccionesObjeto.getCreateOrIngresarOrRemover();
		Iterator iteradorDeInstrucciones = instrucciones.iterator();
		System.out.println("Procesando "+ instrucciones.size()+" instrucciones: "+instruccionesObjeto);
		
		while (iteradorDeInstrucciones.hasNext()){
        	Object instruccionActual = iteradorDeInstrucciones.next();
        	System.out.println("Clase de instruccion:" + instruccionActual.getClass());
        	
        	if (instruccionActual instanceof Create){
        		//Crear nuevo sistema
        		Create instruccionCrear = (Create) instruccionActual;
        		String nombreDelNuevoSistema = instruccionCrear.getNombreArchivo();
        		ColeccionCampos campos = instruccionCrear.getColeccionCampos();
        		this.crearSistema(nombreDelNuevoSistema,campos);
        		
        	} else if (instruccionActual instanceof Ingresar){
        		//Ingresar nuevo Registro
        		Ingresar instruccionIngresar = (Ingresar) instruccionActual;
        		String nombreDelSistema = instruccionIngresar.getNombreArchivo();
        		DefinicionCampos queCampos = instruccionIngresar.getDefinicionCampos();
        		List<Registro> valores = instruccionIngresar.getRegistro();
        		
        		System.out.println("\tIdentifico instruccion \"Ingresar\" en sistema "+nombreDelSistema);
        		Iterator<Registro> iteradorDeRegistros = valores.iterator();
        		while (iteradorDeRegistros.hasNext()){
        			Registro registroAIngresar = iteradorDeRegistros.next();
        			System.out.println("\tRegistro a Ingresar: "+registroAIngresar);
        			this.ingresarRegistroEn(nombreDelSistema, queCampos, registroAIngresar);
        		}       		
        	} else if (instruccionActual instanceof Remover){
        		//Remover un registro con una llave prmaria
        		Remover instruccionRemover = (Remover) instruccionActual;
        		String nombreDelSistema = instruccionRemover.getNombreArchivo();
        		System.out.println("\tIdentifico instruccion \"Remover\" en sistema "+nombreDelSistema);
        		
        		ColeccionLlavesPrimarias coleccionLlavesPrimarias = instruccionRemover.getColeccionLlavesPrimarias();
        		System.out.println("\tColeccion de llaves primarias: "+coleccionLlavesPrimarias);
        		
        		List<String> llavesPrimarias = coleccionLlavesPrimarias.getLlave();
        		System.out.println("\tLa instruccion Remover tiene "+llavesPrimarias.size()+" llaves primarias a ser removidas");
        		Iterator<String> iteradorDeLlavesPrimarias = llavesPrimarias.iterator();
        		
        		while (iteradorDeLlavesPrimarias.hasNext()){
        			String llaveARemover = iteradorDeLlavesPrimarias.next();
        			System.out.println("\tMando a remover llave: "+llaveARemover);
        			this.removerRegistro(nombreDelSistema, llaveARemover);
        		}
        	} else if (instruccionActual instanceof Modificar){
        		//Modificar un registro con una llave primaria
        		Modificar instruccionModificar = (Modificar) instruccionActual;
        		String nombreDelSistema = instruccionModificar.getNombreArchivo();
        		DefinicionCampos queCampos = instruccionModificar.getDefinicionCampos();
        		List<Registro> valores = instruccionModificar.getRegistro();
        		this.modificarRegistro(nombreDelSistema, queCampos, valores);
        	} else if (instruccionActual instanceof Buscar ){
        		Buscar instruccionBuscar = (Buscar) instruccionActual;
        		String nombreDelSistema = instruccionBuscar.getNombreArchivo();
        		System.out.println("\tIdentifico instruccion \"Buscar\" en sistema "+nombreDelSistema);
        		
        		String enQueCampo = instruccionBuscar.getEnCampo();
        		List<String> valoresABuscar = instruccionBuscar.getValor();
        		this.BuscarValorEnCampo(nombreDelSistema,enQueCampo,valoresABuscar);
        	}
        	
        }
	}

	/**
	 * @param nombreDelSistema
	 * @param enQueCampo
	 * @param valoresABuscar
	 * @throws CampoNoEstaIndexado 
	 * @throws CampoEspecificadoNoEstaDefinidoException 
	 * @throws SistemaQueEspecificoNoExisteExcepcion 
	 * @throws RegistroYaTieneCampoIndexado 
	 * @throws IOException 
	 */
	private void BuscarValorEnCampo(String nombreDelSistema, String enQueCampo,
			List<String> valoresABuscar) throws CampoEspecificadoNoEstaDefinidoException, CampoNoEstaIndexado, SistemaQueEspecificoNoExisteExcepcion, IOException, RegistroYaTieneCampoIndexado {
		Sistema sistemaEnDondeBuscar = (Sistema) this.sistemas.get(nombreDelSistema);
		if (sistemaEnDondeBuscar!=null){
			
			System.out.println("\tValores a buscar: "+valoresABuscar);
			List<RegistroFisico> registrosEncontrados = this.sistemas.get(nombreDelSistema).BuscarRegistro(enQueCampo,valoresABuscar);
		} else{
			throw new SistemaQueEspecificoNoExisteExcepcion("no existe el sistema "+nombreDelSistema+" especificado en Buscar");
		}
	}
	/* (non-Javadoc)
	 * @see main.MoculatorServerInterface#removerRegistro(java.lang.String, java.lang.String)
	 */
	@Override
	public void removerRegistro(String nombreSistema, String llavePrimaria) throws SistemaQueEspecificoNoExisteExcepcion, IOException, RegistroYaTieneCampoIndexado {
		Sistema sistemaEnDondeRemover = (Sistema) this.sistemas.get(nombreSistema);
		if (sistemaEnDondeRemover!=null){
			System.out.println("\tIntentando borrar la llave primaria: "+llavePrimaria);
			
			try{
				sistemaEnDondeRemover.removerLlavePrimaria(llavePrimaria);
			} catch (llavePrimariaNoEncontradaExcepcion e){
				System.out.println(e);
			}
		} else{
			throw new SistemaQueEspecificoNoExisteExcepcion("no existe el sistema "+nombreSistema+" especificado en Remover");
		}
	}

	/* (non-Javadoc)
	 * @see main.MoculatorServerInterface#getRequest(java.io.FileInputStream)
	 */
	@Override
	public void getRequest(FileInputStream archivo) throws IOException, SistemaQueEspecificoNoExisteExcepcion, CampoEspecificadoNoEstaDefinidoException, NumeroDeCamposNoCoincidentesConValores, RegistroYaTieneCampoIndexado, CampoNoEstaIndexado {
		try {
			//validar
			//PENDIENTE
			System.out.println("bytes disponibles: "+archivo.available());
			
			// unmarshallear una instancia de las instrucciones
	        Instrucciones instrucciones = ((Instrucciones) this.unmarshaller.unmarshal(archivo));
	        this.procesarRequest(instrucciones);
	        
        } catch( JAXBException je ) {
            je.printStackTrace();
        }
	}

	/* (non-Javadoc)
	 * @see main.MoculatorServerInterface#modificarRegistro(java.lang.String, esquemas.DefinicionCampos, esquemas.Registro)
	 */
	@Override
	public void modificarRegistro(String nombreSistema,
			DefinicionCampos queCampos, Registro registro) {
		// TODO Auto-generated method stub
		
	}
}
